package cz.incad.psh.pshassign.server;

import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
import cz.incad.psh.pshassign.server.search.PSHSearch;
import cz.incad.psh.pshassign.server.search.Synonyms;
import cz.incad.psh.pshassign.server.search.VectorsSearch;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONArray;
import org.json.JSONObject;

/**
 *
 * @author Alberto Hernandez
 */
public class DocAssigner {
    
    private Logger logger = Logger.getLogger(this.getClass().getName());

    HashMap<String, ArrayList<String>> words = new HashMap<String, ArrayList<String>>();
    HashMap<String, ArrayList<String>> roots = new HashMap<String, ArrayList<String>>();
    JSONObject jsonResponse;
    JSONObject jsonHl;
    String strength;
    String docId;
    ArrayList<String> keys;
    List<Candidate> candidates = new ArrayList<Candidate>();
    String titleParam;
    String annotaceParam;
    String konferenceParam;
    String subjectParam;
    private double csPrefLabel;
    private double enPrefLabel;
    private double csAltLabel;
    private double enAltLabel;
    private double titleBoost;
    private double annotaceBoost;
    private double subjectBoost;
    private double konferenceBoost;
    private double boost_exact;
    private double boost_multiple;
    private double boost_proximity;
    private double boost_level;
    private double boost_solr;

    public static void main(String[] args) throws Exception {
        DocAssigner a = new DocAssigner("2347");
        a.assign();
    }

    public DocAssigner(String docId) throws Exception {
        this.docId = docId;

        csPrefLabel = Options.getDouble("assign", "boost_csPrefLabel");
        enPrefLabel = Options.getDouble("assign", "boost_enPrefLabel");
        csAltLabel = Options.getDouble("assign", "boost_csAltLabel");
        enAltLabel = Options.getDouble("assign", "boost_enAltLabel");
        titleBoost = Options.getDouble("assign", "boost_title");
        annotaceBoost = Options.getDouble("assign", "boost_annotace");
        subjectBoost = Options.getDouble("assign", "boost_subjects");
        konferenceBoost = Options.getDouble("assign", "boost_konference");
        boost_exact = Options.getDouble("assign", "boost_exact");
        boost_multiple = Options.getDouble("assign", "boost_multiple");
        boost_proximity = Options.getDouble("assign", "boost_proximity");
        boost_level = Options.getDouble("assign", "boost_level");
        boost_solr = Options.getDouble("assign", "boost_solr");
    }

    public String asXml() throws Exception {
        assign();
        //System.out.println(keys);
        return doXml();
    }

    public String asJson() throws Exception {
        assign();
        JSONObject json = new JSONObject();
        JSONArray jkeys = new JSONArray();
//        int i = 1; 
//        int max = Options.getInt("assign", "max_keys");
        for (Candidate c : candidates) {
            JSONObject k = new JSONObject(c);
            jkeys.put(k);
            //if(i++==max) break;
        }
        json.put("keys", jkeys);
        return json.toString();
    }

    private void assign() throws Exception {
        VectorsSearch vs = new VectorsSearch();
        String json = vs.doSearch(docId);
        JSONObject tvhrJson = new JSONObject(json);
        JSONObject jsonResponse = tvhrJson.getJSONObject("response");
        if (jsonResponse.getInt("numFound") == 0) {
            return;
        }
        JSONObject doc = jsonResponse.getJSONArray("docs").getJSONObject(0);
        titleParam = doc.optString("title", "").toLowerCase();
        annotaceParam = doc.optString("annotace", "").toLowerCase();
        konferenceParam = doc.optString("konference", "").toLowerCase();
        subjectParam = doc.optString("subject", "").toLowerCase();

        PSHSearch search = new PSHSearch();
        JSONObject js = new JSONObject(search.doSearch(tvhrJson));
        strength = Options.getOption("assign", "strength");
        jsonResponse = js.getJSONObject("response");
        JSONObject jsonParams = js.getJSONObject("responseHeader").getJSONObject("params");


        jsonHl = js.getJSONObject("highlighting");
        keys = new ArrayList<String>();
        int numDocs = jsonResponse.getInt("numFound");
        if (numDocs > 0) {
            JSONArray docs = jsonResponse.getJSONArray("docs");



            for (int i = 0; i < docs.length(); i++) {

                JSONObject heslo = docs.getJSONObject(i);
                String root = heslo.getJSONArray("path").getString(0).substring(1).split("/")[0];
                String uri = heslo.getString("uri");
                int level = heslo.getJSONArray("path").getString(0).split("/").length;
                heslo.put("level", level);
//                String label = heslo.getJSONArray("csPrefLabel").getString(0);
//                String path = heslo.getJSONArray("path").getString(0);
//                double score = heslo.getDouble("score");
                JSONObject jsUri = jsonHl.getJSONObject(uri);
                ArrayList<String> ws = setWords(root, jsUri, heslo);
                //Candidate c = new Candidate(label, uri, score, root, jsUri, ws, heslo.getDouble("rank"), path);
                Candidate c = new Candidate(heslo, jsUri, ws, docId);
                candidates.add(c);
            }
            addByStrength();
            filterProximity();
            sortCandidates();
        } else {
            assignByInstitution(tvhrJson);
        }
    }

    private void sortCandidates() {
        Collections.sort(candidates, new Comparator<Candidate>() {
            @Override
            public int compare(Candidate t, Candidate t1) {
                return (t.rank + (t.score * boost_solr) > t1.rank + (t1.score * boost_solr) ? -1 : (t.rank + (t.score * boost_solr) == t1.rank + (t1.score * boost_solr) ? 0 : 1));
            }
        });
    }

    private double calculateRank(String field, String word, JSONObject rank_explain) {
        double res = 0;
        double fscore = 1.0;
        //double ori = 0;
        JSONObject ex = new JSONObject();
        if ("csPrefLabel_hl".equals(field)) {
            fscore = csPrefLabel;
            //ex.put("csPrefLabel", csPrefLabel);
        } else if ("enPrefLabel_hl".equals(field)) {
            fscore = enPrefLabel;
            //ex.put("enPrefLabel", enPrefLabel);
        } else if ("csAltLabel_hl".equals(field)) {
            fscore = csAltLabel;
            //ex.put("csAltLabel", csAltLabel);
        } else if ("enAltLabel_hl".equals(field)) {
            fscore = enAltLabel;
            //ex.put("enAltLabel", enAltLabel);
        }

        ArrayList<String> syns = Synonyms.getSynonyms(word);
        
        boolean sourceFieldKnown = false;
        
        int num = Synonyms.getNumHits(titleParam, syns);
        
        if (num>0) {
            res += fscore * titleBoost * num;
            ex.put("title x " + field, titleBoost + " x " + fscore + " x " + num);
            sourceFieldKnown = true;
        }

        num = Synonyms.getNumHits(annotaceParam, syns);
        if (num>0) {
            res += fscore * annotaceBoost * num;
            ex.put("annotace x " + field, annotaceBoost + " x " + fscore + " x " + num);
            sourceFieldKnown = true;
        }
        num = Synonyms.getNumHits(konferenceParam, syns);
        if (num>0) {
            res += fscore * konferenceBoost * num;
            ex.put("konference x " + field, konferenceBoost + " x " + fscore + " x " + num);
            sourceFieldKnown = true;
        }
        num = Synonyms.getNumHits(subjectParam, syns);
        if (num>0) {
            res += fscore * subjectBoost * num;
            ex.put("subject x " + field, subjectBoost + " x " + fscore + " x " + num);
            sourceFieldKnown = true;
        }
        
        if(!sourceFieldKnown){
            ex.put(field, enAltLabel);
        }

        ex.put("word total", res);

        rank_explain.accumulate(word, ex);
        return res;
    }

    private ArrayList<String> getWords(String cs) {
        ArrayList<String> list = new ArrayList<String>();
        String patternStr = "<b>(.*?)<\\/b>";
        RegExp regExp = RegExp.compile(patternStr, "ig");

        for (MatchResult matcher = regExp.exec(cs); matcher != null; matcher = regExp.exec(cs)) {
            list.add(matcher.getGroup(1));
        }

        return list;
    }

    private int getNumHits(String s) {
        int res = 0;
        String patternStr = "<b>(.*?)<\\/b>";
        RegExp regExp = RegExp.compile(patternStr, "ig");

        for (MatchResult matcher = regExp.exec(s); matcher != null; matcher = regExp.exec(s)) {
            res++;
        }
        return res;
    }

    private int numWords(String s) {
        int res = 0;
        String patternStr = "\\S+";
        RegExp regExp = RegExp.compile(patternStr, "ig");

        for (MatchResult matcher = regExp.exec(s); matcher != null; matcher = regExp.exec(s)) {
            res++;
        }
        return res;

    }

    private int count(final String string, final String substring) {
        int count = 0;
        int idx = 0;

        while ((idx = string.indexOf(substring, idx)) != -1) {
            idx++;
            count++;
        }

        return count;
    }


    private boolean isExact(String hit) {
//        int numHits = getNumHits(hit);
        int numWords = count(hit, " ") + count(hit, "-") + 1;
//        return (numHits == numWords);
        if(numWords==1) return true;
        return cz.incad.psh.pshassign.server.search.ProximitySearch.isNear(hit.replaceAll("<b>", "").replaceAll("</b>", ""), docId);
        
    }

    private boolean isMultiple(String hit) {
        return (getNumHits(hit) > 1);
    }

    private ArrayList<String> setWords(String root, JSONObject uriObject, JSONObject heslo) {
        if (!roots.containsKey(root)) {
            ArrayList rwords = new ArrayList<String>();
            roots.put(root, rwords);
        }
        HashMap<String, ArrayList<String>> wordXfield = new HashMap<String, ArrayList<String>>();
        HashMap<String, String> syns = new HashMap<String, String>();
        double rank = 0;
        boolean exact = false;
        boolean multiple = false;
        JSONObject rank_explain = new JSONObject();
        ArrayList<String> list = new ArrayList<String>();
        Set fields = uriObject.keySet();
        for (Iterator iter2 = fields.iterator(); iter2.hasNext();) {
            String field = (String) iter2.next();
            JSONArray fArray = uriObject.getJSONArray(field);
            for (int k = 0; k < fArray.length(); k++) {
                String hl = fArray.getString(k);
                ArrayList<String> words_in_hit = getWords(hl);
                exact = exact || isExact(hl);
                multiple = multiple || isMultiple(hl);
                if (!roots.containsKey(root)) {
                    ArrayList rwords = new ArrayList<String>();
                    roots.put(root, rwords);
                }

                for (String word1 : words_in_hit) {
                    String word = syns.get(word1);
                    if(word==null){
                        ArrayList<String> wsyn = Synonyms.getSynonyms(word1);
                        word = wsyn.get(0);
                        syns.put(word1, word);
                    }
                    if (wordXfield.containsKey(word)) {
                        
                        if (!wordXfield.get(word).contains(field)) {
                            ArrayList<String> f = wordXfield.get(word);
                            String oldField = f.get(0);
                            double b = getFieldBoost(oldField);
                            double b2 = getFieldBoost(field);
                            if(b<b2){
                                f.set(0, field);
                                //f.add(field);
                                //rank += calculateRank(field, word1, rank_explain);
                            }
                        }
                    } else {
                        ArrayList<String> f = new ArrayList<String>();
                        f.add(field);
                        wordXfield.put(word, f);
                        //rank += calculateRank(field, word1, rank_explain);
                    }

                    if (!list.contains(word)) {
                        list.add(word);
                    }
                    if (words.containsKey(word)) {
                        ArrayList<String> wroots = words.get(word);
                        if (!wroots.contains(root)) {
                            wroots.add(root);
                        }
                    } else {
                        ArrayList<String> wroots = new ArrayList<String>();
                        wroots.add(root);
                        words.put(word, wroots);
                    }


                    if (roots.containsKey(root)) {
                        ArrayList<String> rwords = roots.get(root);
                        if (!rwords.contains(word)) {
                            rwords.add(word);
                        }
                    } else {
                        ArrayList<String> rwords = new ArrayList<String>();
                        rwords.add(word);
                        roots.put(root, rwords);
                    }
                }
            }
        }
        for(String word : wordXfield.keySet()){
            String f = wordXfield.get(word).get(0);
            rank += calculateRank(f, word, rank_explain);
        }
            if (exact) {
                rank = rank * boost_exact;
            }

            if (multiple) {
                rank = rank * boost_multiple;
            }
//        System.out.println("\trank -> " + rank);

        rank += heslo.getInt("level") * boost_level;
        heslo.put("rank", rank);
        heslo.put("rank_explain", rank_explain);
        return list;
    }

    private void addByStrength() {
        if ("multiple".equals(strength)) {
            for (Candidate c : candidates) {
                if (c.isMultiple || c.isExact) {
                    keys.add(c.getLabel());
                }
            }
        } else if ("exact".equals(strength)) {
            for (Candidate c : candidates) {
                if (c.isExact) {
                    keys.add(c.getLabel());
                }
            }
        } else {
            for (Candidate c : candidates) {
                keys.add(c.getLabel());
            }
        }
    }
    double proximity_mean = 0;
    double proximity_median = 0;

    private void filterProximity() {
        int count = 0;
        int kcount = 0;
        int amax = 0;
        int min = 1110;

        Set ks = roots.keySet();
        for (Iterator iter2 = ks.iterator(); iter2.hasNext();) {
            String root = (String) iter2.next();
            //System.out.println("root -> " + root + "   size -> " + roots.get(root).size());
            count += roots.get(root).size();
            amax = Math.max(roots.get(root).size(), amax);
            min = Math.min(roots.get(root).size(), min);
            kcount++;
        }
//        proximity_mean = count / kcount;
//        System.out.println("mean: " + proximity_mean);

        proximity_median = (amax + min) / 2;
//        System.out.println("median: " + proximity_median);

        for (Candidate c : candidates) {
//            int max = 0;
//            for (String word : c.words) {
//                for (String root2 : words.get(word)) {
//                    max = Math.max(roots.get(root2).size(), max);
//                }
//            }
//            System.out.println("key -> " + c.getLabel() + "   max -> " + max);
//            System.out.println("c.getUri: " + c.getUri());
//            System.out.println("c.root: " + c.root);
//            System.out.println("roots.get(c.root): " + roots.get(c.root));
            int prox = roots.get(c.root).size();
            //if(prox<proximity_median){
            //    keys.remove(c.getLabel());
            //}
            c.addProximity(prox - proximity_median, boost_proximity);
        }
    }

    private void assignByInstitution(JSONObject tvhrJson) throws Exception {
        
        logger.log(Level.INFO, "Assigned by institution: " + docId);
        JSONArray docs = tvhrJson.getJSONObject("response").getJSONArray("docs");
        String institution = docs.getJSONObject(0).getJSONArray("institution").getString(0);

        JSONArray a = Institutions.getKeys(institution);
        for (int i = 0; i < a.length(); i++) {
            keys.add(a.getString(i));
        }
    }

    private String doXml() throws Exception {
//        <record xmlns:java="http://xml.apache.org/xslt/java" >
//<controlfield tag="001">92755</controlfield>
//        <datafield ind2="7" ind1=" " tag="650">
//<subfield code="a">ptáci</subfield>
//<subfield code="2">PSH</subfield>
//</datafield>
//<datafield ind2="7" ind1=" " tag="650">
//<subfield code="a">změny</subfield>
//<subfield code="2">PSH</subfield>
//</datafield>

        StringBuilder xml = new StringBuilder();
        xml.append("<record xmlns:java=\"http://xml.apache.org/xslt/java\" >");
        xml.append("<controlfield tag=\"001\">").append(this.docId).append("</controlfield>");


        int i = 1;
        int max = Options.getInt("assign", "max_keys");
        for (Candidate c : candidates) {

            xml.append("<datafield ind2=\"7\" ind1=\"1\" tag=\"650\">");
            xml.append("<subfield code=\"a\">").append(c.getLabel()).append("</subfield>");
            xml.append("<subfield code=\"j\">").append(c.getLabel_en()).append("</subfield>");
            xml.append("<subfield code=\"0\">").append(c.getUri()).append("</subfield>");
            xml.append("<subfield code=\"2\">PSH</subfield>");
            xml.append("</datafield>");
            if (i++ == max) {
                break;
            }
        }
        xml.append("</record>");
        return xml.toString();

    }

    private double getFieldBoost(String field) {
        if ("csPrefLabel_hl".equals(field)) {
            return csPrefLabel;
        } else if ("enPrefLabel_hl".equals(field)) {
            return enPrefLabel;
        } else if ("csAltLabel_hl".equals(field)) {
            return csAltLabel;
        } else if ("enAltLabel_hl".equals(field)) {
            return enAltLabel;
        }else{
            return 0;
        }
    }
}
